/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.util;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;


/**
 * Base implementation of the {@link Lifecycle} interface that implements the
 * state transition rules for {@link Lifecycle#start()} and
 * {@link Lifecycle#stop()}
 */
public abstract class LifecycleBase implements Lifecycle {

	private static final Log log = LogFactory.getLog(LifecycleBase.class);

	private static final StringManager sm = StringManager.getManager(LifecycleBase.class);


	/**
	 * The list of registered LifecycleListeners for event notifications.
	 */
	private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();


	/**
	 * The current state of the source component.
	 */
	private volatile LifecycleState state = LifecycleState.NEW;


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		this.lifecycleListeners.add(listener);
	}


	@Override
	public final synchronized void destroy() throws LifecycleException {
		if (LifecycleState.FAILED.equals(this.state)) {
			try {
				// Triggers clean-up
				this.stop();
			} catch (final LifecycleException e) {
				// Just log. Still want to destroy.
				log.warn(sm.getString(
						"lifecycleBase.destroyStopFail", this.toString()), e);
			}
		}

		if (LifecycleState.DESTROYING.equals(this.state) ||
				LifecycleState.DESTROYED.equals(this.state)) {

			if (log.isDebugEnabled()) {
				final Exception e = new LifecycleException();
				log.debug(sm.getString("lifecycleBase.alreadyDestroyed", this.toString()), e);
			} else if (log.isInfoEnabled() && !(this instanceof Lifecycle.SingleUse)) {
				// Rather than have every component that might need to call
				// destroy() check for SingleUse, don't log an info message if
				// multiple calls are made to destroy()
				log.info(sm.getString("lifecycleBase.alreadyDestroyed", this.toString()));
			}

			return;
		}

		if (!this.state.equals(LifecycleState.STOPPED) &&
				!this.state.equals(LifecycleState.FAILED) &&
				!this.state.equals(LifecycleState.NEW) &&
				!this.state.equals(LifecycleState.INITIALIZED)) {
			this.invalidTransition(Lifecycle.BEFORE_DESTROY_EVENT);
		}

		try {
			this.setStateInternal(LifecycleState.DESTROYING, null, false);
			this.destroyInternal();
			this.setStateInternal(LifecycleState.DESTROYED, null, false);
		} catch (final Throwable t) {
			ExceptionUtils.handleThrowable(t);
			this.setStateInternal(LifecycleState.FAILED, null, false);
			throw new LifecycleException(
					sm.getString("lifecycleBase.destroyFail",this.toString()), t);
		}
	}


	protected abstract void destroyInternal() throws LifecycleException;


	/**
	 * {@inheritDoc}
	 */
	@Override
	public LifecycleListener[] findLifecycleListeners() {
		return this.lifecycleListeners.toArray(new LifecycleListener[0]);
	}


	/**
	 * Allow sub classes to fire {@link Lifecycle} events.
	 *
	 * @param type  Event type
	 * @param data  Data associated with event.
	 */
	protected void fireLifecycleEvent(String type, Object data) {
		final LifecycleEvent event = new LifecycleEvent(this, type, data);
		for (final LifecycleListener listener : this.lifecycleListeners) {
			listener.lifecycleEvent(event);
		}
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public LifecycleState getState() {
		return this.state;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getStateName() {
		return this.getState().toString();
	}


	@Override
	public final synchronized void init() throws LifecycleException {
		if (!this.state.equals(LifecycleState.NEW)) {
			this.invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
		}

		try {
			this.setStateInternal(LifecycleState.INITIALIZING, null, false);
			this.initInternal();
			this.setStateInternal(LifecycleState.INITIALIZED, null, false);
		} catch (final Throwable t) {
			ExceptionUtils.handleThrowable(t);
			this.setStateInternal(LifecycleState.FAILED, null, false);
			throw new LifecycleException(
					sm.getString("lifecycleBase.initFail",this.toString()), t);
		}
	}


	protected abstract void initInternal() throws LifecycleException;


	private void invalidTransition(String type) throws LifecycleException {
		final String msg = sm.getString("lifecycleBase.invalidTransition", type,
				this.toString(), this.state);
		throw new LifecycleException(msg);
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		this.lifecycleListeners.remove(listener);
	}


	/**
	 * Provides a mechanism for sub-classes to update the component state.
	 * Calling this method will automatically fire any associated
	 * {@link Lifecycle} event. It will also check that any attempted state
	 * transition is valid for a sub-class.
	 *
	 * @param state The new state for this component
	 * @throws LifecycleException when attempting to set an invalid state
	 */
	protected synchronized void setState(LifecycleState state)
			throws LifecycleException {
		this.setStateInternal(state, null, true);
	}

	/**
	 * Provides a mechanism for sub-classes to update the component state.
	 * Calling this method will automatically fire any associated
	 * {@link Lifecycle} event. It will also check that any attempted state
	 * transition is valid for a sub-class.
	 *
	 * @param state The new state for this component
	 * @param data  The data to pass to the associated {@link Lifecycle} event
	 * @throws LifecycleException when attempting to set an invalid state
	 */
	protected synchronized void setState(LifecycleState state, Object data)
			throws LifecycleException {
		this.setStateInternal(state, data, true);
	}


	private synchronized void setStateInternal(LifecycleState state,
			Object data, boolean check) throws LifecycleException {

		if (log.isDebugEnabled()) {
			log.debug(sm.getString("lifecycleBase.setState", this, state));
		}

		if (check) {
			// Must have been triggered by one of the abstract methods (assume
			// code in this class is correct)
			// null is never a valid state
			if (state == null) {
				this.invalidTransition("null");
				// Unreachable code - here to stop eclipse complaining about
				// a possible NPE further down the method
				return;
			}

			// Any method can transition to failed
			// startInternal() permits STARTING_PREP to STARTING
			// stopInternal() permits STOPPING_PREP to STOPPING and FAILED to
			// STOPPING
			if (!(state == LifecycleState.FAILED ||
					(this.state == LifecycleState.STARTING_PREP &&
					state == LifecycleState.STARTING) ||
					(this.state == LifecycleState.STOPPING_PREP &&
					state == LifecycleState.STOPPING) ||
					(this.state == LifecycleState.FAILED &&
					state == LifecycleState.STOPPING))) {
				// No other transition permitted
				this.invalidTransition(state.name());
			}
		}

		this.state = state;
		final String lifecycleEvent = state.getLifecycleEvent();
		if (lifecycleEvent != null) {
			this.fireLifecycleEvent(lifecycleEvent, data);
		}
	}


	/**
	 * {@inheritDoc}
	 */
	@Override
	public final synchronized void start() throws LifecycleException {

		if (LifecycleState.STARTING_PREP.equals(this.state) || LifecycleState.STARTING.equals(this.state) ||
				LifecycleState.STARTED.equals(this.state)) {

			if (log.isDebugEnabled()) {
				final Exception e = new LifecycleException();
				log.debug(sm.getString("lifecycleBase.alreadyStarted", this.toString()), e);
			} else if (log.isInfoEnabled()) {
				log.info(sm.getString("lifecycleBase.alreadyStarted", this.toString()));
			}

			return;
		}

		if (this.state.equals(LifecycleState.NEW)) {
			this.init();
		} else if (this.state.equals(LifecycleState.FAILED)) {
			this.stop();
		} else if (!this.state.equals(LifecycleState.INITIALIZED) &&
				!this.state.equals(LifecycleState.STOPPED)) {
			this.invalidTransition(Lifecycle.BEFORE_START_EVENT);
		}

		try {
			this.setStateInternal(LifecycleState.STARTING_PREP, null, false);
			//IMPORT: 容器启动
			this.startInternal();
			if (this.state.equals(LifecycleState.FAILED)) {
				// This is a 'controlled' failure. The component put itself into the
				// FAILED state so call stop() to complete the clean-up.
				this.stop();
			} else if (!this.state.equals(LifecycleState.STARTING)) {
				// Shouldn't be necessary but acts as a check that sub-classes are
				// doing what they are supposed to.
				this.invalidTransition(Lifecycle.AFTER_START_EVENT);
			} else {
				this.setStateInternal(LifecycleState.STARTED, null, false);
			}
		} catch (final Throwable t) {
			// This is an 'uncontrolled' failure so put the component into the
			// FAILED state and throw an exception.
			ExceptionUtils.handleThrowable(t);
			this.setStateInternal(LifecycleState.FAILED, null, false);
			throw new LifecycleException(sm.getString("lifecycleBase.startFail", this.toString()), t);
		}
	}


	/**
	 * Sub-classes must ensure that the state is changed to
	 * {@link LifecycleState#STARTING} during the execution of this method.
	 * Changing state will trigger the {@link Lifecycle#START_EVENT} event.
	 *
	 * If a component fails to start it may either throw a
	 * {@link LifecycleException} which will cause it's parent to fail to start
	 * or it can place itself in the error state in which case {@link #stop()}
	 * will be called on the failed component but the parent component will
	 * continue to start normally.
	 *
	 * @throws LifecycleException Start error occurred
	 */
	protected abstract void startInternal() throws LifecycleException;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final synchronized void stop() throws LifecycleException {

		if (LifecycleState.STOPPING_PREP.equals(this.state) || LifecycleState.STOPPING.equals(this.state) ||
				LifecycleState.STOPPED.equals(this.state)) {

			if (log.isDebugEnabled()) {
				final Exception e = new LifecycleException();
				log.debug(sm.getString("lifecycleBase.alreadyStopped", this.toString()), e);
			} else if (log.isInfoEnabled()) {
				log.info(sm.getString("lifecycleBase.alreadyStopped", this.toString()));
			}

			return;
		}

		if (this.state.equals(LifecycleState.NEW)) {
			this.state = LifecycleState.STOPPED;
			return;
		}

		if (!this.state.equals(LifecycleState.STARTED) && !this.state.equals(LifecycleState.FAILED)) {
			this.invalidTransition(Lifecycle.BEFORE_STOP_EVENT);
		}

		try {
			if (this.state.equals(LifecycleState.FAILED)) {
				// Don't transition to STOPPING_PREP as that would briefly mark the
				// component as available but do ensure the BEFORE_STOP_EVENT is
				// fired
				this.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
			} else {
				this.setStateInternal(LifecycleState.STOPPING_PREP, null, false);
			}

			this.stopInternal();

			// Shouldn't be necessary but acts as a check that sub-classes are
			// doing what they are supposed to.
			if (!this.state.equals(LifecycleState.STOPPING) && !this.state.equals(LifecycleState.FAILED)) {
				this.invalidTransition(Lifecycle.AFTER_STOP_EVENT);
			}

			this.setStateInternal(LifecycleState.STOPPED, null, false);
		} catch (final Throwable t) {
			ExceptionUtils.handleThrowable(t);
			this.setStateInternal(LifecycleState.FAILED, null, false);
			throw new LifecycleException(sm.getString("lifecycleBase.stopFail",this.toString()), t);
		} finally {
			if (this instanceof Lifecycle.SingleUse) {
				// Complete stop process first
				this.setStateInternal(LifecycleState.STOPPED, null, false);
				this.destroy();
			}
		}
	}

	/**
	 * Sub-classes must ensure that the state is changed to
	 * {@link LifecycleState#STOPPING} during the execution of this method.
	 * Changing state will trigger the {@link Lifecycle#STOP_EVENT} event.
	 *
	 * @throws LifecycleException Stop error occurred
	 */
	protected abstract void stopInternal() throws LifecycleException;
}
